﻿using System;
using System.Collections.Generic;
using System.Linq;
using ChiWalker.Utilities;
using Styx;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;

namespace ChiWalker.Helpers
{
    internal static class Clusters
    {
        public static IEnumerable<WoWUnit> NearbyUnfriendlyUnits
        {
            get
            {
                return
                    ObjectManager.GetObjectsOfType<WoWUnit>(false, false).Where(
                        p => p.IsValid() && p.DistanceSqr <= 40*40)
                        .ToList();
            }
        }

        public static IEnumerable<WoWUnit> NearbyUnitsInCombatWithMe
        {
            get
            {
                return
                    ObjectManager.GetObjectsOfType<WoWUnit>(false, false).Where(
                        p =>
                            p.IsValid() && p.DistanceSqr <= 50*50 && p.Combat &&
                            (p.TaggedByMe ||
                             (!p.TaggedByOther &&
                              (p.IsTargetingMyRaidMember || p.IsTargetingMyPartyMember || p.IsTargetingMeOrPet))))
                        .ToList();
            }
        }

        private static bool IsValid(this WoWUnit unit)
        {
            return
                unit.IsAlive && !unit.IsFriendly && !unit.IsCritter && !unit.IsDead && unit.IsValid && unit.CanSelect;
        }

        public static IEnumerable<WoWUnit> UnfriendlyUnitsNearTarget(float distance)
        {
            float dist = distance*distance;
            WoWPoint curTarLocation = StyxWoW.Me.CurrentTarget.Location;
            return ObjectManager.GetObjectsOfType<WoWUnit>(false, false).Where(
                p => p.IsValid && p.Location.DistanceSqr(curTarLocation) <= dist).ToList();
        }

        public static int GetClusterCount(WoWUnit target, IEnumerable<WoWUnit> otherUnits, ClusterType type,
            float clusterRange)
        {
            if (!otherUnits.Any())
                return 0;

            switch (type)
            {
                case ClusterType.Radius:
                    return GetRadiusClusterCount(target, otherUnits, clusterRange);
                case ClusterType.Chained:
                    return GetChainedClusterCount(target, otherUnits, clusterRange);
                case ClusterType.Cone:
                    return GetConeClusterCount(target, otherUnits, clusterRange);
                default:
                    throw new ArgumentOutOfRangeException("type");
            }
        }

        public static WoWUnit GetBestUnitForCluster(IEnumerable<WoWUnit> units, ClusterType type, float clusterRange)
        {
            if (!units.Any())
                return null;

            switch (type)
            {
                case ClusterType.Radius:
                    return (from u in units
                        select new {Count = GetRadiusClusterCount(u, units, clusterRange), Unit = u}).
                        OrderByDescending(a => a.Count).
                        FirstOrDefault().Unit;
                case ClusterType.Chained:
                    return (from u in units
                        select new {Count = GetChainedClusterCount(u, units, clusterRange), Unit = u}).
                        OrderByDescending(a => a.Count).
                        FirstOrDefault().Unit;
                    // coned doesn't have a best unit, since we are the source
                default:
                    throw new ArgumentOutOfRangeException("type");
            }
        }

        private static int GetConeClusterCount(WoWUnit target, IEnumerable<WoWUnit> otherUnits, float distance)
        {
            WoWPoint targetLoc = target.Location;
            return otherUnits.Count(u => target.IsSafelyFacing(u, 90f) && u.Location.Distance(targetLoc) <= distance);
            // most (if not all) cone spells are 90 degrees
        }

        private static int GetRadiusClusterCount(WoWUnit target, IEnumerable<WoWUnit> otherUnits, float radius)
        {
            WoWPoint targetLoc = target.Location;
            return otherUnits.Count(u => u.Location.DistanceSqr(targetLoc) <= radius*radius);
        }

        private static int GetChainedClusterCount(WoWUnit target, IEnumerable<WoWUnit> otherUnits, float chainRange)
        {
            IEnumerable<int> unitCounters =
                otherUnits.Select(u => GetUnitsChainWillJumpTo(target, otherUnits.ToList(), chainRange).Count);

            return unitCounters.Max() + 1;
        }

        private static List<WoWUnit> GetUnitsChainWillJumpTo(WoWUnit target, List<WoWUnit> otherUnits, float chainRange)
        {
            WoWPoint targetLoc = target.Location;
            ulong targetGuid = target.Guid;
            for (int i = otherUnits.Count - 1; i >= 0; i--)
            {
                if (otherUnits[i].Guid == targetGuid || otherUnits[i].Location.DistanceSqr(targetLoc) > chainRange)
                    otherUnits.RemoveAt(i);
            }
            return otherUnits;
        }
    }
}